/*
 * hd44780_i2c_lcd.c
 *
 * Copyright 2022 dh33ex <dh33ex@riseup.net>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA 02110-1301, USA or see <http://www.gnu.org/licenses/>.
 *
 *
 */

#ifndef __msp430_h_
    #include <msp430.h>
#endif
#include "hd44780_i2c_lcd.h"


/*
 * P0 - RS
 * P1 - R/W
 * P2 - E
 * P3 - LIGHT
 * P4 - DB4
 * P5 - DB5
 * P6 - DB6
 * P7 - DB7
 */

#define PCF8574_RS      0x01
#define PCF8574_RW      0x02
#define PCF8574_E       0x04
#define PCF8574_LIGHT   0x08
#define PCF8574_DB4     0x10
#define PCF8574_DB5     0x20
#define PCF8574_DB6     0x40
#define PCF8574_DB7     0x80

#define HD44780_CLEARDISPLAY    0x01
#define HD44780_RETURNHOME      0x02

#define HD44780_ENTRYMODESET    0x04
#define HD44780_ID      0x02
#define HD44780_S       0x01

#define HD44780_DISPLAYCONTROL  0x08
#define HD44780_D       0x04
#define HD44780_C       0x02
#define HD44780_B       0x01

#define HD44780_SHIFTSETUP      0x10
#define HD44780_SC      0x08
#define HD44780_RL      0x04

#define HD44780_FUNCTIONSET     0x20
#define HD44780_F       0x04
#define HD44780_N       0x08
#define HD44780_DL      0x10

#define HD44780_DDRAM_ADDRESS   0x80
#define HD44780_CGRAM_ADDRESS   0x40

#define HD44780_DATA        1
#define HD44780_INSTRUCTION 0

char i2c_buffer;


void PCF8574_send(unsigned char byte);
void HD44780_send(HD44780 *disp, unsigned char type, unsigned char data);


void PCF8574_send(unsigned char byte) {
    while (UCB0STAT & UCBUSY);          /* wait until I2C is free */
    i2c_buffer = byte;

    IFG2 &= ~UCB0TXIFG;                 /* clear transmit interrupt flag */

    UCB0CTL1 |= UCTXSTT;                /* send start message manually */
    UCB0TXBUF = i2c_buffer;             /* load TX buffer */
    while (!(IFG2 & UCB0TXIFG));
    UCB0CTL1 |= UCTXSTP;                /* send stop message manually */
    while (UCB0CTL1 & UCTXSTP);         /* wait until stop message sent */
    __delay_cycles(1500);
}


void HD44780_send(HD44780 *disp, unsigned char type, unsigned char data) {
    PCF8574_send((data & 0xF0) | (type ? PCF8574_RS : 0) | PCF8574_E);
    PCF8574_send((data & 0xF0) | (type ? PCF8574_RS : 0));
    PCF8574_send((data << 4) | (type ? PCF8574_RS : 0) | PCF8574_E);
    PCF8574_send((data << 4) | (type ? PCF8574_RS : 0) | PCF8574_LIGHT * disp->light);
}


void LCD_init(HD44780 *disp) {
    /* setup B0 for I2C */
    UCB0CTL1 |= UCSWRST;                /* put B0 in SW RST */

    UCB0CTL1 |= UCSSEL_2;               /* choose SMCLK */
    UCB0BR0 = 10;                       /* set presalar to 10 */
    UCB0BR1 = 0;

    UCB0CTL0 |= UCMODE_3;               /* put into I2C mode */
    UCB0CTL0 |= UCMST;                  /* set as master */
    UCB0I2CSA = disp->address;          /* set slave address */

    /* setup ports */
    P1SEL |= BIT7;                      /* P1.7 = SDA */
    P1SEL2 |= BIT7;
    P1SEL |= BIT6;                      /* P1.6 = SCL */
    P1SEL2 |= BIT6;

    UCB0CTL1 |= UCTR;                   /* set transmit mode */

    UCB0CTL1 &= ~UCSWRST;               /* take B0 out of SW RST */

    /* init display */
    __delay_cycles(40000);                          /* delay 40 ms */
    PCF8574_send(PCF8574_DB5 | PCF8574_DB4 | PCF8574_E);
    PCF8574_send(PCF8574_DB5 | PCF8574_DB4);
    __delay_cycles(4100);                           /* delay 4.1 ms */
    PCF8574_send(PCF8574_DB5 | PCF8574_DB4 | PCF8574_E);
    PCF8574_send(PCF8574_DB5 | PCF8574_DB4);
    __delay_cycles(1000);                           /* delay 1 us */
    PCF8574_send(PCF8574_DB5 | PCF8574_DB4 | PCF8574_E);
    PCF8574_send(PCF8574_DB5 | PCF8574_DB4);
    PCF8574_send(PCF8574_DB5 | PCF8574_E);
    PCF8574_send(PCF8574_DB5);

    HD44780_send(disp, HD44780_INSTRUCTION, HD44780_FUNCTIONSET | (disp->rows == 1 ? 0 : HD44780_N));
    HD44780_send(disp, HD44780_INSTRUCTION, HD44780_DISPLAYCONTROL | HD44780_D | (disp->cursor ==  HD44780_NO_CURSOR ? 0 : (disp->cursor == HD44780_BLINK_CURSOR ? HD44780_B : HD44780_C)));
    HD44780_send(disp, HD44780_INSTRUCTION, HD44780_CLEARDISPLAY);
    HD44780_send(disp, HD44780_INSTRUCTION, HD44780_ENTRYMODESET | HD44780_ID);
}


void LCD_clear(HD44780 *disp) {
    HD44780_send(disp, HD44780_INSTRUCTION, HD44780_CLEARDISPLAY);
}


void LCD_backlight(HD44780 *disp, unsigned char state) {
    disp->light = state;
    PCF8574_send(PCF8574_LIGHT*disp->light);
}


void LCD_write(HD44780 *disp, char *string) {
    PCF8574_send(0x0);
    while (*string != '\0') {
        HD44780_send(disp, HD44780_DATA, *(string++));
    }
    __delay_cycles(10);
    PCF8574_send(disp->light * PCF8574_LIGHT);
}


void LCD_set_address(HD44780 *disp, unsigned char x, unsigned char y) {
    unsigned char x_start[] = {0x00, 0x40, disp->columns, 0x40+disp->columns};
    HD44780_send(disp, HD44780_INSTRUCTION, HD44780_DDRAM_ADDRESS | (x_start[y] + x));
}


#if defined(__TI_COMPILER_VERSION__) || defined(__IAR_SYSTEMS_ICC__)
#pragma vector = USCIAB0TX_VECTOR
__interrupt void USCIAB0TX_I2C_ISR(void) {
#elif defined(__GNUC__)
void __attribute__ ((interrupt(USCIAB0TX_VECTOR))) USCIAB0TX_I2C_ISR (void) {
#else
#error Compiler not supported!
#endif
    UCB0TXBUF = i2c_buffer;                 /* load TX buffer */
    while (!(IFG2 & UCB0TXIFG));
    IE2 &= ~UCB0TXIE;                       /* disable interrupts */
    IFG2 &= ~UCB0TXIFG;                     /* clear USCI_B0 TX int flag */
    __bic_SR_register_on_exit(LPM0_bits);   /* exit LPM0 */
}
